/*-------------------------------------------------------------------------
 *
 * hbafuncs.c
 *	  认证文件 SQL 视图的支持函数。
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  src/backend/utils/adt/hbafuncs.c
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include "catalog/objectaddress.h"
#include "common/ip.h"
#include "funcapi.h"
#include "libpq/hba.h"
#include "miscadmin.h"
#include "utils/array.h"
#include "utils/builtins.h"
#include "utils/guc.h"


static ArrayType *get_hba_options(HbaLine *fc_hba);
static void fc_fill_hba_line(Tuplestorestate *fc_tuple_store, TupleDesc fc_tupdesc,
						  int fc_lineno, HbaLine *fc_hba, const char *fc_err_msg);
static void fc_fill_hba_view(Tuplestorestate *fc_tuple_store, TupleDesc fc_tupdesc);
static void fc_fill_ident_line(Tuplestorestate *fc_tuple_store, TupleDesc fc_tupdesc,
							int fc_lineno, IdentLine *fc_ident, const char *fc_err_msg);
static void fc_fill_ident_view(Tuplestorestate *fc_tuple_store, TupleDesc fc_tupdesc);


/*
 * 此宏指定与任何给定的认证方法支持的最大身份验证选项数量。
 * 目前LDAP支持12个，并且有3个不依赖于
 * 此处的auth方法。实际上可能无法同时设置所有这些，
 * 但我们将宏值设置得足够高，以便谨慎并避免静态分析工具的警告。
 */
#define MAX_HBA_OPTIONS 15

/*
 * 创建一个文本数组，列出HBA行中指定的选项。
 * 如果未指定任何选项，则返回NULL。
 */
static ArrayType * get_hba_options(HbaLine *fc_hba)
{
	int			fc_noptions;
	Datum		fc_options[MAX_HBA_OPTIONS];

	fc_noptions = 0;

	if (fc_hba->auth_method == uaGSS || fc_hba->auth_method == uaSSPI)
	{
		if (fc_hba->include_realm)
			fc_options[fc_noptions++] =
				CStringGetTextDatum("include_realm=true");

		if (fc_hba->krb_realm)
			fc_options[fc_noptions++] =
				CStringGetTextDatum(psprintf("krb_realm=%s", fc_hba->krb_realm));
	}

	if (fc_hba->usermap)
		fc_options[fc_noptions++] =
			CStringGetTextDatum(psprintf("map=%s", fc_hba->usermap));

	if (fc_hba->clientcert != clientCertOff)
		fc_options[fc_noptions++] =
			CStringGetTextDatum(psprintf("clientcert=%s", (fc_hba->clientcert == clientCertCA) ? "verify-ca" : "verify-full"));

	if (fc_hba->pamservice)
		fc_options[fc_noptions++] =
			CStringGetTextDatum(psprintf("pamservice=%s", fc_hba->pamservice));

	if (fc_hba->auth_method == uaLDAP)
	{
		if (fc_hba->ldapserver)
			fc_options[fc_noptions++] =
				CStringGetTextDatum(psprintf("ldapserver=%s", fc_hba->ldapserver));

		if (fc_hba->ldapport)
			fc_options[fc_noptions++] =
				CStringGetTextDatum(psprintf("ldapport=%d", fc_hba->ldapport));

		if (fc_hba->ldapscheme)
			fc_options[fc_noptions++] =
				CStringGetTextDatum(psprintf("ldapscheme=%s", fc_hba->ldapscheme));

		if (fc_hba->ldaptls)
			fc_options[fc_noptions++] =
				CStringGetTextDatum("ldaptls=true");

		if (fc_hba->ldapprefix)
			fc_options[fc_noptions++] =
				CStringGetTextDatum(psprintf("ldapprefix=%s", fc_hba->ldapprefix));

		if (fc_hba->ldapsuffix)
			fc_options[fc_noptions++] =
				CStringGetTextDatum(psprintf("ldapsuffix=%s", fc_hba->ldapsuffix));

		if (fc_hba->ldapbasedn)
			fc_options[fc_noptions++] =
				CStringGetTextDatum(psprintf("ldapbasedn=%s", fc_hba->ldapbasedn));

		if (fc_hba->ldapbinddn)
			fc_options[fc_noptions++] =
				CStringGetTextDatum(psprintf("ldapbinddn=%s", fc_hba->ldapbinddn));

		if (fc_hba->ldapbindpasswd)
			fc_options[fc_noptions++] =
				CStringGetTextDatum(psprintf("ldapbindpasswd=%s",
											 fc_hba->ldapbindpasswd));

		if (fc_hba->ldapsearchattribute)
			fc_options[fc_noptions++] =
				CStringGetTextDatum(psprintf("ldapsearchattribute=%s",
											 fc_hba->ldapsearchattribute));

		if (fc_hba->ldapsearchfilter)
			fc_options[fc_noptions++] =
				CStringGetTextDatum(psprintf("ldapsearchfilter=%s",
											 fc_hba->ldapsearchfilter));

		if (fc_hba->ldapscope)
			fc_options[fc_noptions++] =
				CStringGetTextDatum(psprintf("ldapscope=%d", fc_hba->ldapscope));
	}

	if (fc_hba->auth_method == uaRADIUS)
	{
		if (fc_hba->radiusservers_s)
			fc_options[fc_noptions++] =
				CStringGetTextDatum(psprintf("radiusservers=%s", fc_hba->radiusservers_s));

		if (fc_hba->radiussecrets_s)
			fc_options[fc_noptions++] =
				CStringGetTextDatum(psprintf("radiussecrets=%s", fc_hba->radiussecrets_s));

		if (fc_hba->radiusidentifiers_s)
			fc_options[fc_noptions++] =
				CStringGetTextDatum(psprintf("radiusidentifiers=%s", fc_hba->radiusidentifiers_s));

		if (fc_hba->radiusports_s)
			fc_options[fc_noptions++] =
				CStringGetTextDatum(psprintf("radiusports=%s", fc_hba->radiusports_s));
	}

	/* 如果添加更多选项，请考虑增加MAX_HBA_OPTIONS。 */
	Assert(fc_noptions <= MAX_HBA_OPTIONS);

	if (fc_noptions > 0)
		return construct_array(fc_options, fc_noptions, TEXTOID, -1, false, TYPALIGN_INT);
	else
		return NULL;
}

/* pg_hba_file_rules视图中的列数 */
#define NUM_PG_HBA_FILE_RULES_ATTS	 9

/*
 * fill_hba_line
 *		构建pg_hba_file_rules视图的一行，将其添加到tuplestore。
 *
 * tuple_store: 存储数据的位置
 * tupdesc: 视图的元组描述符
 * lineno: pg_hba.conf行号（必须始终有效）
 * hba: 解析的行数据（可以为NULL，在这种情况下err_msg应该被设置）
 * err_msg: 错误消息（如果没有则为NULL）
 *
 * 注意：泄漏内存，但我们不在乎，因为这在短期内存
 * 上下文中运行。
 */
static void fc_fill_hba_line(Tuplestorestate *fc_tuple_store, TupleDesc fc_tupdesc,
			  int fc_lineno, HbaLine *fc_hba, const char *fc_err_msg)
{
	Datum		fc_values[NUM_PG_HBA_FILE_RULES_ATTS];
	bool		fc_nulls[NUM_PG_HBA_FILE_RULES_ATTS];
	char		fc_buffer[NI_MAXHOST];
	HeapTuple	fc_tuple;
	int			fc_index;
	ListCell   *fc_lc;
	const char *fc_typestr;
	const char *fc_addrstr;
	const char *fc_maskstr;
	ArrayType  *fc_options;

	Assert(fc_tupdesc->natts == NUM_PG_HBA_FILE_RULES_ATTS);

	memset(fc_values, 0, sizeof(fc_values));
	memset(fc_nulls, 0, sizeof(fc_nulls));
	fc_index = 0;

	/* 行号 */
	fc_values[fc_index++] = Int32GetDatum(fc_lineno);

	if (fc_hba != NULL)
	{
		/* type */
		/* 避免默认情况，以便编译器会警告缺少的情况 */
		fc_typestr = NULL;
		switch (fc_hba->conntype)
		{
			case ctLocal:
				fc_typestr = "local";
				break;
			case ctHost:
				fc_typestr = "host";
				break;
			case ctHostSSL:
				fc_typestr = "hostssl";
				break;
			case ctHostNoSSL:
				fc_typestr = "hostnossl";
				break;
			case ctHostGSS:
				fc_typestr = "hostgssenc";
				break;
			case ctHostNoGSS:
				fc_typestr = "hostnogssenc";
				break;
		}
		if (fc_typestr)
			fc_values[fc_index++] = CStringGetTextDatum(fc_typestr);
		else
			fc_nulls[fc_index++] = true;

		/* database */
		if (fc_hba->databases)
		{
			/*
			 * 将AuthToken列表扁平化为字符串列表。虽然似乎我们
			 * 应该重新引用任何带引号的令牌，但这一点已被拒绝，
			 * 理由是这使得将数组
			 * 元素与其他系统目录进行比较变得更加困难。这使得像
			 * "all"或"samerole"这样的条目在形式上模糊......但以这种方式命名
			 * 数据库/角色的用户正自我造成痛苦。
			 */
			List	   *fc_names = NIL;

			foreach(fc_lc, fc_hba->databases)
			{
				AuthToken  *fc_tok = lfirst(fc_lc);

				fc_names = lappend(fc_names, fc_tok->string);
			}
			fc_values[fc_index++] = PointerGetDatum(strlist_to_textarray(fc_names));
		}
		else
			fc_nulls[fc_index++] = true;

		/* user */
		if (fc_hba->roles)
		{
			/* 将AuthToken列表扁平化为字符串列表；参见上面的注释 */
			List	   *fc_roles = NIL;

			foreach(fc_lc, fc_hba->roles)
			{
				AuthToken  *fc_tok = lfirst(fc_lc);

				fc_roles = lappend(fc_roles, fc_tok->string);
			}
			fc_values[fc_index++] = PointerGetDatum(strlist_to_textarray(fc_roles));
		}
		else
			fc_nulls[fc_index++] = true;

		/* 地址和子网掩码 */
		/* 避免默认情况，以便编译器会警告缺少的情况 */
		fc_addrstr = fc_maskstr = NULL;
		switch (fc_hba->ip_cmp_method)
		{
			case ipCmpMask:
				if (fc_hba->hostname)
				{
					fc_addrstr = fc_hba->hostname;
				}
				else
				{
					/*
					 * 注意：如果pg_getnameinfo_all失败，它将设置缓冲区为
					 * "???", 我们想要返回这个。
					 */
					if (fc_hba->addrlen > 0)
					{
						if (pg_getnameinfo_all(&fc_hba->addr, fc_hba->addrlen,
											   fc_buffer, sizeof(fc_buffer),
											   NULL, 0,
											   NI_NUMERICHOST) == 0)
							clean_ipv6_addr(fc_hba->addr.ss_family, fc_buffer);
						fc_addrstr = pstrdup(fc_buffer);
					}
					if (fc_hba->masklen > 0)
					{
						if (pg_getnameinfo_all(&fc_hba->mask, fc_hba->masklen,
											   fc_buffer, sizeof(fc_buffer),
											   NULL, 0,
											   NI_NUMERICHOST) == 0)
							clean_ipv6_addr(fc_hba->mask.ss_family, fc_buffer);
						fc_maskstr = pstrdup(fc_buffer);
					}
				}
				break;
			case ipCmpAll:
				fc_addrstr = "all";
				break;
			case ipCmpSameHost:
				fc_addrstr = "samehost";
				break;
			case ipCmpSameNet:
				fc_addrstr = "samenet";
				break;
		}
		if (fc_addrstr)
			fc_values[fc_index++] = CStringGetTextDatum(fc_addrstr);
		else
			fc_nulls[fc_index++] = true;
		if (fc_maskstr)
			fc_values[fc_index++] = CStringGetTextDatum(fc_maskstr);
		else
			fc_nulls[fc_index++] = true;

		/* 认证方法 */
		fc_values[fc_index++] = CStringGetTextDatum(hba_authname(fc_hba->auth_method));

		/* 选项 */
		fc_options = get_hba_options(fc_hba);
		if (fc_options)
			fc_values[fc_index++] = PointerGetDatum(fc_options);
		else
			fc_nulls[fc_index++] = true;
	}
	else
	{
		/* 没有解析结果，因此将相关字段设置为null */
		memset(&fc_nulls[1], true, (NUM_PG_HBA_FILE_RULES_ATTS - 2) * sizeof(bool));
	}

	/* 错误 */
	if (fc_err_msg)
		fc_values[NUM_PG_HBA_FILE_RULES_ATTS - 1] = CStringGetTextDatum(fc_err_msg);
	else
		fc_nulls[NUM_PG_HBA_FILE_RULES_ATTS - 1] = true;

	fc_tuple = heap_form_tuple(fc_tupdesc, fc_values, fc_nulls);
	tuplestore_puttuple(fc_tuple_store, fc_tuple);
}

/*
 * fill_hba_view
 *		读取 pg_hba.conf 文件并用视图记录填充元组存储。
 */
static void fc_fill_hba_view(Tuplestorestate *fc_tuple_store, TupleDesc fc_tupdesc)
{
	FILE	   *fc_file;
	List	   *fc_hba_lines = NIL;
	ListCell   *fc_line;
	MemoryContext fc_linecxt;
	MemoryContext fc_hbacxt;
	MemoryContext fc_oldcxt;

	/*
	 * 在不太可能的情况下，如果我们无法打开 pg_hba.conf，我们将抛出一个
	 * 错误，而不是试图通过某种视图条目报告它。
	 * （大多数其他错误条件应该在视图条目中产生消息。）
	 */
	fc_file = AllocateFile(HbaFileName, "r");
	if (fc_file == NULL)
		ereport(ERROR,
				(errcode_for_file_access(),
				 errmsg("could not open configuration file \"%s\": %m",
						HbaFileName)));

	fc_linecxt = tokenize_auth_file(HbaFileName, fc_file, &fc_hba_lines, DEBUG3);
	FreeFile(fc_file);

	/* 现在解析所有行 */
	fc_hbacxt = AllocSetContextCreate(CurrentMemoryContext,
								   "hba parser context",
								   ALLOCSET_SMALL_SIZES);
	fc_oldcxt = MemoryContextSwitchTo(fc_hbacxt);
	foreach(fc_line, fc_hba_lines)
	{
		TokenizedAuthLine *fc_tok_line = (TokenizedAuthLine *) lfirst(fc_line);
		HbaLine    *fc_hbaline = NULL;

		/* 不解析已经有错误的行 */
		if (fc_tok_line->err_msg == NULL)
			fc_hbaline = parse_hba_line(fc_tok_line, DEBUG3);

		fc_fill_hba_line(fc_tuple_store, fc_tupdesc, fc_tok_line->line_num,
					  fc_hbaline, fc_tok_line->err_msg);
	}

	/* 释放标记器内存 */
	MemoryContextDelete(fc_linecxt);
	/* 释放 parse_hba_line 的内存 */
	MemoryContextSwitchTo(fc_oldcxt);
	MemoryContextDelete(fc_hbacxt);
}

/*
 * pg_hba_file_rules
 *
 * 可通过 SQL 访问的集合返回函数，返回 pg_hba.conf 文件中的所有条目。
 */
Datum pg_hba_file_rules(PG_FUNCTION_ARGS)
{
	ReturnSetInfo *fc_rsi;

	/*
	 * 构建元组存储来保存结果行。我们必须使用材料化模式，以确保在
	 * 游标打开时对 HBA 文件更改安全。与每次都查找解析列表中
	 * 的当前位置相比，这也是更高效的。
	 */
	InitMaterializedSRF(fcinfo, 0);

	/* 填充元组存储 */
	fc_rsi = (ReturnSetInfo *) fcinfo->resultinfo;
	fc_fill_hba_view(fc_rsi->setResult, fc_rsi->setDesc);

	PG_RETURN_NULL();
}

/* pg_ident_file_mappings 视图中的列数 */
#define NUM_PG_IDENT_FILE_MAPPINGS_ATTS	 5

/*
 * fill_ident_line: 构建 pg_ident_file_mappings 视图的一行，将其添加到
 * 元组存储中
 *
 * tuple_store: 存储数据的位置
 * tupdesc: 视图的元组描述符
 * lineno: pg_ident.conf 行号（必须始终有效）
 * ident: 解析行数据（可以为 NULL，此时应设置 err_msg）
 * err_msg: 错误消息（如果没有则为 NULL）
 *
 * 注意：内存泄漏，但我们不在乎，因为这是在短暂的内存上下文中运行的。
 */
static void fc_fill_ident_line(Tuplestorestate *fc_tuple_store, TupleDesc fc_tupdesc,
				int fc_lineno, IdentLine *fc_ident, const char *fc_err_msg)
{
	Datum		fc_values[NUM_PG_IDENT_FILE_MAPPINGS_ATTS];
	bool		fc_nulls[NUM_PG_IDENT_FILE_MAPPINGS_ATTS];
	HeapTuple	fc_tuple;
	int			fc_index;

	Assert(fc_tupdesc->natts == NUM_PG_IDENT_FILE_MAPPINGS_ATTS);

	memset(fc_values, 0, sizeof(fc_values));
	memset(fc_nulls, 0, sizeof(fc_nulls));
	fc_index = 0;

	/* 行号 */
	fc_values[fc_index++] = Int32GetDatum(fc_lineno);

	if (fc_ident != NULL)
	{
		fc_values[fc_index++] = CStringGetTextDatum(fc_ident->usermap);
		fc_values[fc_index++] = CStringGetTextDatum(fc_ident->ident_user);
		fc_values[fc_index++] = CStringGetTextDatum(fc_ident->pg_role);
	}
	else
	{
		/* 没有解析结果，因此将相关字段设置为null */
		memset(&fc_nulls[1], true, (NUM_PG_IDENT_FILE_MAPPINGS_ATTS - 2) * sizeof(bool));
	}

	/* 错误 */
	if (fc_err_msg)
		fc_values[NUM_PG_IDENT_FILE_MAPPINGS_ATTS - 1] = CStringGetTextDatum(fc_err_msg);
	else
		fc_nulls[NUM_PG_IDENT_FILE_MAPPINGS_ATTS - 1] = true;

	fc_tuple = heap_form_tuple(fc_tupdesc, fc_values, fc_nulls);
	tuplestore_puttuple(fc_tuple_store, fc_tuple);
}

/*
 * 读取 pg_ident.conf 文件并用视图记录填充元组存储。
 */
static void fc_fill_ident_view(Tuplestorestate *fc_tuple_store, TupleDesc fc_tupdesc)
{
	FILE	   *fc_file;
	List	   *fc_ident_lines = NIL;
	ListCell   *fc_line;
	MemoryContext fc_linecxt;
	MemoryContext fc_identcxt;
	MemoryContext fc_oldcxt;

	/*
	 * 在不太可能的情况下，如果我们无法打开 pg_ident.conf，我们将抛出一个
	 * 错误，而不是试图通过某种视图条目报告它。
	 * （大多数其他错误条件应该在视图条目中产生消息。）
	 */
	fc_file = AllocateFile(IdentFileName, "r");
	if (fc_file == NULL)
		ereport(ERROR,
				(errcode_for_file_access(),
				 errmsg("could not open usermap file \"%s\": %m",
						IdentFileName)));

	fc_linecxt = tokenize_auth_file(IdentFileName, fc_file, &fc_ident_lines, DEBUG3);
	FreeFile(fc_file);

	/* 现在解析所有行 */
	fc_identcxt = AllocSetContextCreate(CurrentMemoryContext,
									 "ident parser context",
									 ALLOCSET_SMALL_SIZES);
	fc_oldcxt = MemoryContextSwitchTo(fc_identcxt);
	foreach(fc_line, fc_ident_lines)
	{
		TokenizedAuthLine *fc_tok_line = (TokenizedAuthLine *) lfirst(fc_line);
		IdentLine  *fc_identline = NULL;

		/* 不解析已经有错误的行 */
		if (fc_tok_line->err_msg == NULL)
			fc_identline = parse_ident_line(fc_tok_line, DEBUG3);

		fc_fill_ident_line(fc_tuple_store, fc_tupdesc, fc_tok_line->line_num, fc_identline,
						fc_tok_line->err_msg);
	}

	/* 释放标记器内存 */
	MemoryContextDelete(fc_linecxt);
	/* 释放 parse_ident_line 的内存 */
	MemoryContextSwitchTo(fc_oldcxt);
	MemoryContextDelete(fc_identcxt);
}

/*
 * 可通过 SQL 访问的 SRF，返回 pg_ident.conf 文件中的所有条目。
 */
Datum pg_ident_file_mappings(PG_FUNCTION_ARGS)
{
	ReturnSetInfo *fc_rsi;

	/*
	 * 构建元组存储来保存结果行。我们必须使用材料化模式，以确保在
	 * 游标打开时对 HBA 文件更改安全。与每次都查找解析列表中
	 * 的当前位置相比，这也是更高效的。
	 */
	InitMaterializedSRF(fcinfo, 0);

	/* 填充元组存储 */
	fc_rsi = (ReturnSetInfo *) fcinfo->resultinfo;
	fc_fill_ident_view(fc_rsi->setResult, fc_rsi->setDesc);

	PG_RETURN_NULL();
}
